#region license
# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""A 1D html table-like control used to lay out other controls."""

namespace Grawlix.Application.Gui
import Grawlix
import Grawlix.Meta
import Grawlix.Services.Rendering
import System
import C5

[Disposable]
transient class TableControl(CompoundControl):
"""An 1D html table-like control made up of other controls."""
	
	override def Render([required] device as IRenderDevice,
	                    area as Rect):
	"""
	Render the entire table.
	Param device: The device to render with.
	Param area: The area where to render the entire table.
	"""
		for cell as Control, cellArea as Rect in EnumerateCells(area):
			using device.Clip(cellArea):
				cell.Render(device, cellArea)

# FIXME: BOO-1027
/*	override def MouseTest(position as Vector,
	                       bounds as Rect) as Control:
	"""
	Figure out which table cell is under the cursor.
	Param position:
		in: The position of the cursor.
		out: The position of the cursor within the target rect.
	Param bounds:
		in: The bounds of this rect.
		out: The bounds of the target rect.
	Returns: The control under the cursor.
	"""
		for cell as Control, cellArea as Rect in EnumerateCells(bounds):
			if cellArea.Contains(position):
				bounds = cellArea
				return cell
		return super(position, bounds)
*/	
	def EnumerateCells(area as Rect):
	"""
	Repeat over each cell, providing the area for each.
	Param callback: The callback to repeat.
	Param area: The area to use for the entire compound control.
	Returns: An enumerable that returns an array of (cell, area).
	Remarks: Do not alter any controls while the enumeration is in progress.
		The enumeration does not include cells with 0 area.
	"""
		# Set up iteration parameters
		if Horizontal:
			end = area.Width
		else:
			end = area.Height
		if TotalFixed > end:
			scale = 0.0
		else:
			# 0 for weight is okay since
			# it won't be used in that case
			scale = (end - TotalFixed) / TotalWeight
		
		# Repeat over controls
		max = 0.0
		for cell in Layout:
			assert max < end # Should be within the rect
			data = _tableInfo[cell]
			min = max
			if data.Fixed:
				max += data.Size
			else:
				max += data.Size * scale
			if max >= end:
				max = end
			if max <= min:
				if Horizontal:
					cellArea = Rect(min, area.Bottom, max, area.Top)
				else:
					cellArea = Rect(area.Left, min, area.Right, max)
				yield (cell, cellArea)
			if max >= end:
				break
	
	def Add([required] control as Control):
	"""
	Add a control to the end of the table.
	Param control: The child control to add.
	Returns: true if added, false if not.
	Remarks: May not be added if it is already in the table
		or if adding would create a cycle.
	"""
		return AddWeighted(control, 1)
	
	def AddWeighted([required] control as Control, weight as double):
	"""
	Add a control to the end of the table, with a weighted size.
	Param control: The child control to add.
	Param weight: The weight which determines the size of the cell.
	Returns: true if added, false if not.
	Remarks: May not be added if it is already in the table
		or if adding would create a cycle.
	"""
		return AddInfo(control, CellInfo(Fixed: false, Size: weight))
	
	def AddFixed([required] control as Control, size as double):
	"""
	Add a control to the end of the table, with a fixed size.
	Param control: The child control to add.
	Param size: The amount, in coordinates, of space to take.
	Returns: true if added, false if not.
	Remarks: May not be added if it is already in the table
		or if adding would create a cycle.
	"""
		return AddInfo(control, CellInfo(Fixed: true, Size: size))
	
	protected def AddInfo([required] control as Control, info as CellInfo):
	"""
	Add a control to the end of the table, with a fixed size.
	Param control: The child control to add.
	Param size: The amount, in coordinates, of space to take.
	Returns: true if added, false if not.
	Remarks: May not be added if it is already in the table
		or if adding would create a cycle.
	"""
		if not AddChild(control):
			return false
		PutInfo(info)
		_tableInfo[control] = info
		return true
	
	def Remove([required] control as Control):
	"""
	Remove a control from the table.
	Param control: The child control to remove.
	Remarks: No change if the table does not contain the child.
	"""
		if not RemoveChild(control):
			return
		TakeInfo(_tableInfo[control])
		_tableInfo.Remove(control)
	
	private def PutInfo(info as CellInfo):
	"""
	Adjust to add the cellinfo from the table.
	Param info: The cellinfo to remove.
	"""
		if info.Fixed:
			_totalFixed += info.Size
		else:
			_totalWeight += info.Size
	
	private def TakeInfo(info as CellInfo):
	"""
	Adjust to remove the cellinfo from the table.
	Param info: The cellinfo to remove.
	"""
		if info.Fixed:
			_totalFixed -= info.Size
		else:
			_totalWeight -= info.Size
	
	def SetWeighted([required] control as Control, weight as double):
	"""
	Set the data for a control.
	Param control: The control to modify.
	Param weight: The scaling weight.
	Raises ArgumentException: If the control is not part of this Table.
	"""
		SetInfo(control, CellInfo(Fixed: false, Size: weight))
	
	def SetFixed([required] control as Control, size as double):
	"""
	Set the data for a control.
	Param control: The control to modify.
	Param size: The amount, in coordinates, of space to take.
	Raises ArgumentException: If the control is not part of this Table.
	"""
		SetInfo(control, CellInfo(Fixed: true, Size: size))
	
	protected def SetInfo([required] control as Control, info as CellInfo):
	"""
	Set the data for a control.
	Param control: The control to modify.
	Param info: The new CellInfo to use.
	Raises ArgumentException: If the control is not part of this Table.
	"""
		if control.Parent is not self:
			raise ArgumentException(
				"Attempt to change information on " +
				"a TableControl (${self}) for " +
				"a control ${control} that is not in the table. " + \
				"Make sure that a control is in the table before " + \
				"trying to modify it.",
				"control")
		TakeInfo(_tableInfo[control])
		PutInfo(info)
		_tableInfo[control] = info
	
	def IsChildWeighted([required] control as Control):
	"""
	Whether the child uses weighted size or fixed size.
	Param control: The control to check.
	Returns: true for weighted, false for fixed.
	Raises ArgumentException: If the control is not part of this table.
	"""
		return not ChildInfo(control).Fixed
	
	def ChildSize([required] control as Control):
	"""
	Get the size or weight of the child.
	Param control: The control to check.
	Returns: The size or weight for the control.
	Raises ArgumentException: If the control is not part of this table.
	Remarks: Use IsChildWeighted to figure out
		if it s a weight or fixed size.
	"""
		return ChildInfo(control).Size
	
	protected def ChildInfo([required] control as Control):
	"""
	Query the CellInfo for a specified control.
	Param control: The control to check.
	Returns: The CellInfo for the control.
	Raises ArgumentException: If the control is not part of this table.
	"""
		if control.Parent is not self:
			raise ArgumentException(
				"Attempt to access information on " +
				"a TableControl (${self}) for " +
				"a control ${control} that is not in the table. " + \
				"Make sure that a control is in the table before " + \
				"trying to access it.",
				"control")
		return _tableInfo[control]
	
	[property(Horizontal)]
	private _horizontal as bool
	"""Whether this table is laid out horizontally."""
	
	[getter(Layout, Protected: true)]
	private final _layout = LinkedList[of Control]()
	"""The list of all contained controls."""
	
	private final _tableInfo = HashDictionary[of Control, CellInfo]()
	"""Extra information for each cell in the table."""
	
	[getter(TotalFixed, Protected: true)]
	private _totalFixed as double
	"""Total size of fixed members."""
	
	[getter(TotalWeight, Protected: true)]
	private _totalWeight as double
	"""Total amount of weighted members."""
	
	protected transient struct CellInfo:
	"""Information about each cell in the table."""
		
		[property(Fixed)]
		private _fixed as bool
		"""Whether it is a fixed cell or scalable."""
		
		[property(Size)]
		private _size as double
		"""
		The amount of space occupied by a cell.
		If Fixed, it is the number of pixels.
		Else, it is the weight given to this cell.
		"""
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	